In [1]:
numbers = [1, 2, 3, 4]
squares = (n ** 2 for n in numbers)

In [2]:
1 in squares


Out[2]:
True

In [3]:
9 in squares


Out[3]:
True

In [4]:
4 in squares


Out[4]:
False

In [5]:
16 in squares


Out[5]:
False

In [6]:
numbers = [1, 2, 3, 4]
squares = (n ** 2 for n in numbers)

In [7]:
16 in squares


Out[7]:
True

In [8]:
1 in squares


Out[8]:
False

In [9]:
def uniques_only(iterable):
    """
    Function that accepts an iterable and returns a new iterable 
    with all items from the original iterable except for duplicates.
    """
    uniques = []
    uniques_dict = {}
    
    for item in iterable:
        if item not in uniques_dict:
            uniques_dict[item] = 1
            uniques.append(item)
    return uniques

In [10]:
uniques_only([1, 2, 2, 1, 1, 3, 2, 1])


Out[10]:
[1, 2, 3]

In [12]:
nums = [1, -3, 2, 3, -1]
squares = (n**2 for n in nums)

In [13]:
uniques_only(squares)


Out[13]:
[1, 9, 4]

Bonus1: return an iterator (for example a generator) from your uniques_only function instead of a list


In [15]:
def uniques_only(iterable):
    """
    Function that accepts an iterable and returns a new iterable 
    with all items from the original iterable except for duplicates.
    """
    uniques_dict = {}
    
    for item in iterable:
        if item not in uniques_dict:
            uniques_dict[item] = 1
            yield item

In [16]:
list(uniques_only([1, 2, 2, 1, 1, 3, 2, 1]))


Out[16]:
[1, 2, 3]

In [17]:
nums = [1, -3, 2, 3, -1]
squares = (n**2 for n in nums)

In [18]:
list(uniques_only(squares))


Out[18]:
[1, 9, 4]

In [19]:
uniques_only([1, 2, 2, 1, 1, 3, 2, 1])


Out[19]:
<generator object uniques_only at 0x0000000004745BA0>

In [23]:
list(uniques_only({'a': 1, 'b':2, 'c': 3}))


Out[23]:
['b', 'c', 'a']

Bonus2: allow your uniques_only function to work with unhashable objects


In [21]:
list(uniques_only([['a', 'b'], ['a', 'c'], ['a', 'b']]))


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-8861ad0221c0> in <module>()
----> 1 list(uniques_only([['a', 'b'], ['a', 'c'], ['a', 'b']]))

<ipython-input-15-30fe6f29b7dc> in uniques_only(iterable)
      7 
      8     for item in iterable:
----> 9         if item not in uniques_dict:
     10             uniques_dict[item] = 1
     11             yield item

TypeError: unhashable type: 'list'

In [44]:
def uniques_only(iterable):
    """
    Function that accepts an iterable and returns a new iterable 
    with all items from the original iterable except for duplicates.
    """
    uniques_dict = {}
    
    for item in iterable:
        item = tuple(item) 
        if item not in uniques_dict:
            uniques_dict[item] = 1
            yield item

In [45]:
list(uniques_only([['a', 'b'], ['a', 'c'], ['a', 'b']]))


Out[45]:
[('a', 'b'), ('a', 'c')]

In [46]:
list(uniques_only([1, 2, 2, 1, 1, 3, 2, 1]))


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-46-412ec3e275a0> in <module>()
----> 1 list(uniques_only([1, 2, 2, 1, 1, 3, 2, 1]))

<ipython-input-44-2d83fbb942c7> in uniques_only(iterable)
      7 
      8     for item in iterable:
----> 9         item = tuple(item)
     10         if item not in uniques_dict:
     11             uniques_dict[item] = 1

TypeError: 'int' object is not iterable

In [68]:
def uniques_only(iterable):
    """
    Function that accepts an iterable and returns a new iterable 
    with all items from the original iterable except for duplicates.
    """
    uniques_dict = {}
    
    for item in iterable:
        try:
            hashed = tuple(item)
        except TypeError:
            hashed = item
            
        if hashed not in uniques_dict:
            uniques_dict[hashed] = 1
            yield item

In [69]:
list(uniques_only([1, 2, 2, 1, 1, 3, 2, 1]))


Out[69]:
[1, 2, 3]

In [70]:
nums = [1, -3, 2, 3, -1]
squares = (n**2 for n in nums)

In [71]:
list(uniques_only(squares))


Out[71]:
[1, 9, 4]

In [72]:
list(uniques_only([['a', 'b'], ['a', 'c'], ['a', 'b']]))


Out[72]:
[['a', 'b'], ['a', 'c']]

In [75]:
from timeit import default_timer
import unittest

class UniquesOnlyTests(unittest.TestCase):

    """Tests for uniques_only."""

    def assertIterableEqual(self, iterable1, iterable2):
        self.assertEqual(list(iterable1), list(iterable2))

    def test_no_duplicates(self):
        self.assertIterableEqual(uniques_only([1, 2, 3]), [1, 2, 3])

    def test_adjacent_duplicates(self):
        self.assertIterableEqual(uniques_only([1, 1, 2, 2, 3]), [1, 2, 3])

    def test_non_adjacent_duplicates(self):
        self.assertIterableEqual(uniques_only([1, 2, 3, 1, 2]), [1, 2, 3])

    def test_lots_of_duplicates(self):
        self.assertIterableEqual(uniques_only([1, 2, 2, 1, 1, 2, 1]), [1, 2])

    def test_accepts_iterator(self):
        nums = (n**2 for n in [1, 2, 3])
        self.assertIterableEqual(uniques_only(nums), [1, 4, 9])

    # To test the Bonus part of this exercise, comment out the following line
    # @unittest.expectedFailure
    def test_returns_iterator(self):
        nums = iter([1, 2, 3])
        output = uniques_only(nums)
        self.assertEqual(iter(output), iter(output))
        self.assertEqual(next(output), 1)
        self.assertEqual(next(nums), 2)

    # To test the Bonus part of this exercise, comment out the following line
    # @unittest.expectedFailure
    def test_accepts_nonhashable_types(self):
        output = uniques_only([[1, 2], [3], [1], [3]])
        self.assertIterableEqual(output, [[1, 2], [3], [1]])

    # To test the Bonus part of this exercise, comment out the following line
    # @unittest.expectedFailure
    def test_hashable_types_faster(self):
        hashables = [(n,) for n in range(4000)] + [0]
        unhashables = [[n] for n in range(4000)] + [0]
        with Timer() as hashable:
            for _ in uniques_only(hashables):
                pass
        with Timer() as unhashable:
            for _ in uniques_only(unhashables):
                pass
        self.assertLess(hashable.elapsed * 5, unhashable.elapsed)


class Timer:

    """Context manager to time a code block."""

    def __enter__(self):
        self.start = default_timer()
        return self

    def __exit__(self, *args):
        self.end = default_timer()
        self.elapsed = self.end - self.start


if __name__ == "__main__":
    unittest.main(argv=['first-arg-is-ignored'], exit=False)


...F....
======================================================================
FAIL: test_hashable_types_faster (__main__.UniquesOnlyTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-75-5820734c9de5>", line 53, in test_hashable_types_faster
    self.assertLess(hashable.elapsed * 5, unhashable.elapsed)
AssertionError: 0.005672213156913131 not less than 0.0016248737268824698

----------------------------------------------------------------------
Ran 8 tests in 0.007s

FAILED (failures=1)

In [ ]: